package zetcode.games.tetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class TetrisPanel extends JPanel implements ActionListener, TetrisConstants{
	
	Timer timer;
	boolean isFallingFinished = false;
	boolean isStarted = false;
	boolean isPaused = false;
	int numLinesRemoved = 0;
	int curX = 0;
	int curY = 0;
	JLabel statusBar;
	Shape curPiece;
	Tetrominoe[] board;

	public TetrisPanel(Tetris parent){
		setFocusable(true);
		curPiece = new Shape();
		timer = new Timer(400, this);
		timer.start();
		
		statusBar = parent.getStatusBar();
		board = new Tetrominoe[BoardWidth * BoardHeight];
		addKeyListener(new TAdapter());
		clearBoard();
	}
	
	@Override
	public void actionPerformed(ActionEvent e){
		if(isFallingFinished){
			isFallingFinished = false;
			newPiece();
		}
		else{oneLineDown();}
	}
	
	int squareWidth(){return (int) getSize().getWidth() / BoardWidth;}
	int squareHeight(){return (int) getSize().getHeight() / BoardHeight;}
	Tetrominoe shapeAt(int x, int y){return board[y*BoardWidth + x];}
	
	public void start(){
		if(isPaused){return;}
		isStarted = true;
		isFallingFinished = false;
		numLinesRemoved = 0;
		clearBoard();
		
		newPiece();
		timer.start();
	}
	
	private void pause(){
		if(!isStarted){return;}
		isPaused = !isPaused;
		if(isPaused){
			timer.stop();
			statusBar.setText("Paused");
		}
		else{
			timer.start();
			statusBar.setText(String.valueOf(numLinesRemoved));
		}
		repaint();
	}
	
	public void paint(Graphics g){
		super.paint(g);
		
		Dimension size = getSize();
		int boardTop = (int) size.getHeight() - BoardHeight * squareHeight();
		
		for(int i = 0; i < BoardHeight; i++){
			for(int j = 0; j < BoardWidth; j++){
				Tetrominoe shape = shapeAt(j, BoardHeight - i - 1);
				if(shape != Tetrominoe.NoShape){
					drawSquare(g, j*squareWidth(), boardTop + i*squareHeight(), shape);
				}
			}
		}
		
		if(curPiece.getShape() != Tetrominoe.NoShape){
			for(int i = 0; i < 4; i++){
				int x = curX + curPiece.x(i);
				int y = curY + curPiece.y(i);
				drawSquare(g, x*squareWidth(), boardTop + (BoardHeight - y - 1) * squareHeight(), curPiece.getShape());
			}
		}
	}
	
	private void dropDown(){
		int newY = curY;
		while(newY > 0){
			if(!tryMove(curPiece, curX, newY - 1)){break;}
			newY--;
		}
		pieceDropped();
	}
	
	private void oneLineDown(){
		if(!tryMove(curPiece, curX, curY - 1)){pieceDropped();}
	}
	
	private void clearBoard(){
		for(int i = 0; i < BoardHeight * BoardWidth; i++){
			board[i] = Tetrominoe.NoShape;
		}
	}
	
	private void pieceDropped(){
		for(int i = 0; i < 4; i++){
			int x = curX + curPiece.x(i);
			int y = curY + curPiece.y(i);
			board[y*BoardWidth + x] = curPiece.getShape();
		}
		
		removeFullLines();
		if(!isFallingFinished){newPiece();}
	}
	
	private void newPiece(){
		curPiece.setRandomShape();
		curX = BoardWidth/2 + 1;
		curY = BoardHeight - 2 + curPiece.minY();
		
		if(!tryMove(curPiece, curX, curY)){
			curPiece.setShape(Tetrominoe.NoShape);
			timer.stop();
			isStarted = false;
			statusBar.setText("Game Over - Score: " + numLinesRemoved);
		}
	}
	
	private boolean tryMove(Shape newPiece, int dX, int dY){
		for(int i = 0; i < 4; i++){
			int x = dX + newPiece.x(i);
			int y = dY + newPiece.y(i);
			if(x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight){return false;}
			if(shapeAt(x, y) != Tetrominoe.NoShape){return false;}
		}
		
		curPiece = newPiece;
		curX = dX;
		curY = dY;
		repaint();
		return true;
	}
	
	private void removeFullLines(){
		int numFullLines = 0;
		
		for(int i = BoardHeight - 1; i >= 0; i--){
			boolean lineIsFull = true;
			
			for(int j = 0; j < BoardWidth; j++){
				if(shapeAt(j, i) == Tetrominoe.NoShape){
					lineIsFull = false;
					break;
				}
			}
			
			if(lineIsFull){
				numFullLines++;
				for(int k = i; k < BoardHeight - 1; k++){
					for(int j = 0; j < BoardWidth; j++){
						board[k*BoardWidth + j] = shapeAt(j, k+1);
					}
				}
			}
		}
		if(numFullLines > 0){
			numLinesRemoved += numFullLines;
			statusBar.setText(String.valueOf(numLinesRemoved));
			isFallingFinished = true;
			curPiece.setShape(Tetrominoe.NoShape);
			repaint();
		}
	}
	
	private void drawSquare(Graphics g, int x, int y, Tetrominoe shape){		
		Color color = shape.getColor();
		
		g.setColor(color);
		g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
		
		g.setColor(color.brighter());
		g.drawLine(x, y + squareHeight() - 1, x, y);
		g.drawLine(x, y, x + squareWidth() - 1, y);
		
		g.setColor(color.darker());
		g.drawLine(x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1);
		g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1);
	}
	
	class TAdapter extends KeyAdapter{
		public void keyPressed(KeyEvent e){
			if(!isStarted || curPiece.getShape() == Tetrominoe.NoShape){return;}
			
			int keyCode = e.getKeyCode();
			
			if(keyCode == KeyEvent.VK_P){
				pause();
				return;
			}
			
			if(isPaused){return;}
			
			switch(keyCode){
			case KeyEvent.VK_LEFT:
				tryMove(curPiece, curX - 1, curY);
				break;
			case KeyEvent.VK_RIGHT:
				tryMove(curPiece, curX + 1, curY);
				break;
			case KeyEvent.VK_DOWN:
				tryMove(curPiece.rotateRight(), curX, curY);
				break;
			case KeyEvent.VK_UP:
				tryMove(curPiece.rotateLeft(), curX, curY);
				break;
			case KeyEvent.VK_SPACE:
				dropDown();
				break;
			case KeyEvent.VK_D:
				oneLineDown();
				break;
			}
		}
	}
}
